/* eslint-disable no-continue */
const fs = require('fs-extra');
const _ = require('underscore');
const logger = require('../../logger');
const utility = require('../../utility');
const files = require('../../files');
const functions = require('../../functions');
const { addTest } = require('../../tests');

async function importTcfTests(tcfPath, version, zip = true) {
  const { _id: versionId } = version;
  const testsDir = `${tcfPath}.dest`;
  let tcfFiles;
  await fs.remove(testsDir);
  if (zip) {
    await utility.uncompress(tcfPath, testsDir);
    tcfFiles = await utility.getFiles(testsDir, ['.tcf']);
  } else {
    tcfFiles = [tcfPath];
  }
  logger.verbose(tcfFiles);
  const allFileMap = await files.getSourceFileByVersionId(versionId, null, { _id: 1, path: 1 }, 'path');
  const allFuncMap = await functions.getFunctionByVersionId(versionId, {
    _id: 1, fileId: 1, functionName: 1, functionAttrs: 1, kind: 1,
  });
  const testDatas = _.flatten(await Promise.all(tcfFiles.map(async (tcf) => {
    const tcfStr = await fs.readFile(tcf, 'utf-8');
    // eslint-disable-next-line no-use-before-define
    return tcfJson2TestData(tcf2Json(tcfStr));
  }))).filter((testData) => {
    const fileExisted = allFileMap.get(testData.filePath);
    if (!fileExisted) {
      return false;
    }
    testData.fileId = fileExisted._id;
    const funcExisted = [...allFuncMap.values()]
      .find((fun) => fun.functionName === testData.functionName && fun.fileId.toString() === testData.fileId.toString());
    if (!funcExisted) {
      return false;
    }
    testData.functionId = funcExisted._id;
    return true;
  });
  logger.debug(testDatas);
  await utility.arrayChunkOperation(testDatas, 20, async (testData) => {
    await addTest(version.projectId, versionId, testData.fileId, testData.functionId, {
      data: testData.data,
    });
  });
}

/**
 * 按行分割字符串
 * @param {string} str 要分割的字符串
 * @returns {string[]} 分割后的字符串数组
 */
function splitByLine(str) {
  // 使用正则表达式匹配 \n 或 \r\n
  const regex = /\r?\n/;

  // 将字符串按照换行符分割成数组
  const lines = str.split(regex);

  // 如果字符串以换行符结尾,则最后一个元素为空字符串,需要去掉
  if (lines[lines.length - 1] === '') {
    lines.pop();
  }

  return lines;
}

function beginMatch(str) {
  const match = str.match(/^# Begin (.*)/);
  if (match) {
    if (!match[1]) {
      throw new Error('tag empty');
    }
    return match[1];
  }
  return null;
}
function endMatch(str) {
  const match = str.match(/^# End (.*)/);
  if (match) {
    if (!match[1]) {
      throw new Error('tag empty');
    }
    return match[1];
  }
  return null;
}
function equalExpressionMatch(str) {
  const regex = /^([^=]+)=(.*)$/;
  const match = str.match(regex);
  if (match) {
    return [match[1], match[2]];
  }
  return null;
}
/**
 * 拆分路径，适配结构体、数组
 * @param {string} keyPath a.b[0].c
 * @returns {[string]} 拆分后的路径 ['a', '@value', 'b', '@value', '0', '@value', 'c']
 */
function splitPath(keyPath) {
  // eslint-disable-next-line no-useless-escape
  const regex = /([^\[\].]+)/g;
  return keyPath.match(regex).join('.@value.').split('.');
}

function tcf2Json(tcfStr, resultObj = {}, trace = []) {
  logger.verbose(trace);
  const lines = splitByLine(tcfStr);
  if (!lines.length) {
    return resultObj;
  }
  let currentTag;
  let cacheLines = [];
  for (let cursor = 0; cursor < lines.length; cursor += 1) {
    const line = lines[cursor].trim();
    if (currentTag) { // 之前已经匹配到begin tag并且尚未匹配到end tag
      if (endMatch(line) === currentTag) { // 匹配到end tag
        if (currentTag === 'Overloading') {
          // 特殊处理
          resultObj[currentTag].push(...cacheLines);
        } else if (currentTag === 'Startup code') {
          // 特殊处理
          resultObj[currentTag].push(cacheLines.join('\n'));
        } else {
          const obj = {};
          tcf2Json(cacheLines.join('\n'), obj, [...trace, currentTag]);
          resultObj[currentTag].push(obj);
        }

        currentTag = undefined;
        cacheLines = [];
      } else {
        cacheLines.push(line);
      }
      continue;
    }
    const beginTag = beginMatch(line);
    if (beginTag) { // 匹配到begin tag
      currentTag = beginTag;
      resultObj[currentTag] = resultObj[currentTag] || [];
      if (!Array.isArray(resultObj[currentTag])) {
        resultObj[currentTag] = [];
      }
      continue;
    }
    const equalExpression = equalExpressionMatch(line);
    if (equalExpression) {
      const [k, v] = equalExpression;
      resultObj[k.trim()] = v.trim();
    } else {
      logger.warn(`not equal expression under ${trace}:`, line);
      resultObj[cursor] = line;
    }
  }

  if (cacheLines && cacheLines.length) {
    // 未匹配到end tag
    throw new Error('not matched end tag',);
  }
  return resultObj;
}
function tcfJson2TestData(tcfJson) {
  const testCases = tcfJson['Test Case'] || [];
  const outputArr = [];
  testCases.forEach((tc) => {
    const data = {};
    // 初始化代码。用例导出tcf时，malloc赋值会转化为startup code
    if (tc['Startup code'] && tc['Startup code'].length) {
      data.prelude = tc['Startup code'].join('\n');
    }
    if (tc.Variable) {
      tc.Variable.forEach((v) => {
        if (!v.Value) {
          return;
        }
        // usage 使用标记，Z表示形参、G表示全局变量、O表示输出、H表示全局输出
        switch (v.Usage) {
          case 'O':
            utility.setValueByKeyPathArray(data, ['output', ...splitPath(v.Name), '@value'], v.Value);
            break;
          case 'Z':
            utility.setValueByKeyPathArray(data, ['variables', 'params', ...splitPath(v.Name), '@value'], v.Value);
            break;
          case 'G':
            utility.setValueByKeyPathArray(data, ['variables', 'global', ...splitPath(v.Name), '@value'], v.Value);
            break;
          case 'H':
            utility.setValueByKeyPathArray(data, ['output', 'global', ...splitPath(v.Name), '@value'], v.Value);
            break;
          default:
            break;
        }
      });
    }

    // 桩函数
    if (tc['TC Stub']) {
      tc['TC Stub'].forEach((stub) => {
        const returnVal = stub['TC Stub Return Value'][0];
        if (returnVal) {
          const vals = returnVal.Value.split(',');
          vals.forEach((val, idx) => {
            // 返回值
            if (val) {
              utility.setValueByKeyPathArray(data, ['stubs', stub.Procedure, '@value', idx, '%', '@value'], val);
            }
          });
          // 调用次数
          utility.setValueByKeyPathArray(data, ['stubs', stub.Procedure, 'times'], vals.length - 1);
          // 暂时只支持赋值打桩
          utility.setValueByKeyPathArray(data, ['stubs', stub.Procedure, 'kind'], 1);
        }
      });
    }

    outputArr.push({
      filePath: tc.File,
      functionName: tc.Procedure,
      data,
    });
  });
  return outputArr;
}

module.exports = {
  importTcfTests,
};
